package com.cloud.tools.redis.aspect;

import com.cloud.common.core.asserts.ApiAssert;
import com.cloud.common.core.enums.ErrorCodeEnum;
import com.cloud.common.redis.utils.RedisUtils;
import com.cloud.tools.redis.annotation.RedisStoreLock;
import com.cloud.tools.redis.config.ExpressionEvaluator;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.expression.EvaluationContext;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * 分布式aop库存锁
 * 拦截注解@RedisLock下的接口，统一上锁
 *
 * @author yan
 * @see RedisStoreLock
 */
@Slf4j
@Aspect
@Component
public class RedisStoreLockAspect {

    private ExpressionEvaluator evaluator = new ExpressionEvaluator();

    @Autowired
    private RedisUtils redisUtils;

    @Pointcut("@annotation(com.cloud.tools.redis.annotation.RedisStoreLock)")
    @SuppressWarnings("EmptyMethod")
    public void pointCut() {
    }

    @Around(value = "pointCut()&&@annotation(redisLock)")
    public Object around(ProceedingJoinPoint point, RedisStoreLock redisLock) throws Throwable {
        String field = redisLock.field();
        //解析field 判断是否有el表达式
        field = resolveKey(point, field, field.indexOf("#"));

        //秒杀的商品键值
        String storeKey = redisLock.storeKey();
        //解析storeKey 判断是否有el表达式
        storeKey = resolveKey(point, storeKey, storeKey.indexOf("#"));
        //是否获取到令牌 无就循环获取
        boolean acquire;
        do {
            //当前库存数量
            String store = redisUtils.get(storeKey);
            ApiAssert.isFalse(ErrorCodeEnum.INTERNAL_SERVER_ERROR.convert(redisLock.errMsg()),
                    StringUtils.isBlank(store) || Integer.parseInt(store) <= 0);

            //true代表 获取到锁 并设置业务处理时间timeout  过期后自动解锁
            acquire = redisUtils.setIfAbsent(field, storeKey, redisLock.timeout(), TimeUnit.SECONDS);
            if (!acquire) {
                //休息一下 在继续获取
                long sleep = redisLock.sleep() > 10 ? redisLock.sleep() : 10;
                Thread.sleep(sleep);
            }
        } while (!acquire);
        return point.proceed();

    }

    /**
     * 解析Key 包括el解析
     *
     * @param point
     * @param key
     * @param indexOf
     * @return
     */
    private String resolveKey(ProceedingJoinPoint point, String key, int indexOf) {
        //key = user_#user.id
        if (indexOf == -1) {
            return key;
        }
        //截取#user.id
        String needResolveStr = key.substring(key.indexOf("#"));

        //解析el表达式 得到user.id值 1002
        EvaluationContext evaluationContext = evaluator.createEvaluationContext(point.getTarget(), point.getTarget().getClass(), ((MethodSignature) point.getSignature()).getMethod(), point.getArgs());
        AnnotatedElementKey methodKey = new AnnotatedElementKey(((MethodSignature) point.getSignature()).getMethod(), point.getTarget().getClass());
        Object condition = evaluator.condition(needResolveStr, methodKey, evaluationContext, String.class);
        //最终返回 user_1002
        return key.substring(0, indexOf) + condition.toString();
    }

}

